/**
 * Copyright (C) 2008 by The Regents of the University of California
 * Redistribution of this file is permitted under the terms of the GNU
 * Public License (GPL).
 *
 * @author Junghoo "John" Cho <cho AT cs.ucla.edu>
 * @date 3/24/2008
 */

#include <cstdio>
#include <iostream>
#include <fstream>
#include "Bruinbase.h"
#include "SqlEngine.h"
#include "BTreeIndex.h"

using namespace std;

// external functions and variables for load file and sql command parsing 
extern FILE* sqlin;
int sqlparse(void);


RC SqlEngine::run(FILE* commandline)
{
  fprintf(stdout, "Bruinbase> ");

  // set the command line input and start parsing user input
  sqlin = commandline;
  sqlparse();  // sqlparse() is defined in SqlParser.tab.c generated from
               // SqlParser.y by bison (bison is GNU equivalent of yacc)

  return 0;
}

RC SqlEngine::select(int attr, const string& table, const vector<SelCond>& cond)
{
  RecordFile rf;   // RecordFile containing the table
  RecordId   rid;  // record cursor for table scanning
  //In case there is an index
  BTreeIndex indexFile;

  RC     rc;
  int    key;     
  string value;
  int    count;
  int    diff;

  // open the table file
  if ((rc = rf.open(table + ".tbl", 'r')) < 0) {
    fprintf(stderr, "Error: table %s does not exist\n", table.c_str());
    return rc;
  }

  bool useIndex = false;

  //check if there is a index for this table
  if((indexFile.open(table + ".idx", 'r')) == 0)
  {
	  useIndex = true;
  }

  if(useIndex)
  {
	  //keep a vector for string comparisons
		//to be done last
	  //vector<SelCond> stringCond

	  //find boundaries for key search
	  int min_key;
	  bool min_keySet = false;
	  bool minKeyEquals = false;
	  int max_key;
	  bool max_keySet = false;
	  bool maxKeyEquals = false;
	  //since no duplicate keys, can only have one equal key
	  bool equalKey = false;
	  int key_to_equal;
	  bool notEqualKey = false;
	  vector<int> keys_to_not_equal;

	  string min_value;
	  bool min_valueSet = false;
	  bool minValueEquals = false;
	  string max_value;
	  bool max_valueSet = false;
	  bool maxValueEquals = false;
	  bool equalValue = false;
	  string value_to_equal;
	  bool notEqualValue = false;
	  vector<string> values_to_not_equal;

	  int curKey;
	  string curValue;
	  int type;

	  bool empty = false;
	  bool cont = true;

	 for (unsigned i = 0; i < cond.size(); i++)
	 {
		 //cerr << "in conditions" << endl;
		switch (cond[i].attr)
		{
		case 1:
			type = 1;
			curKey = atoi(cond[i].value);
			break;
		case 2:
			type = 2;
			curValue = cond[i].value;
			break;
		}

		switch (cond[i].comp)
		{
		case SelCond::EQ:
			if(type == 1)
			{
				//a tuple cannot equal the value of two different keys
				if(equalKey == true && key_to_equal != curKey)
				{
					empty = true;
					goto checkall;
				}
				else
				{
					//cerr << equalKey << endl;
					equalKey = true;
					key_to_equal = curKey;
				}
			}
			else
			{
				if(equalValue == true && (strcmp(value_to_equal.c_str(), curValue.c_str()) != 0))
				{
					empty = true;
					goto checkall;
				}
				else
				{
					equalValue = true;
					value_to_equal = curValue;
				}
			}
			break;
		case SelCond::NE:
			if(type == 1)
			{
				if(equalKey && curKey == key_to_equal)
				{
					empty = true;
					goto checkall;
				}
				notEqualKey = true;
				keys_to_not_equal.push_back(curKey);
			}
			else
			{
				if(equalValue && (strcmp(curValue.c_str(), value_to_equal.c_str()) == 0))
				{
					empty = true;
					goto checkall;
				}
				notEqualValue = true;
				values_to_not_equal.push_back(curValue);
			}
			break;
		case SelCond::GT:
			if(type == 1)
			{
				if(min_keySet == true)
				{
					if(curKey > min_key)
					{
						min_key = curKey;
						minKeyEquals = false;
					}
					else if(curKey == min_key && minKeyEquals == true)
					{
						minKeyEquals = false;
					}
				}
				else
				{
					min_keySet = true;
					min_key = curKey;
					minKeyEquals = false;
				}
			}
			else
			{
				if(min_valueSet == true)
				{
					if(strcmp(curValue.c_str(), min_value.c_str()) > 0)
					{
						min_value = curValue;
						minValueEquals = false;
					}
					else if(strcmp(curValue.c_str(), min_value.c_str()) == 0
						&& minValueEquals == true)
					{
						minValueEquals = false;
					}
				}
				else
				{
					min_valueSet = true;
					min_value = curValue;
					minValueEquals = false;
				}
			}
			break;
		case SelCond::LT:
			if(type == 1)
			{
				if(max_keySet == true)
				{
					if(curKey < max_key)
					{
						max_key = curKey;
						maxKeyEquals = false;
					}
					else if(curKey == max_key && maxKeyEquals == true)
					{
						maxKeyEquals = false;
					}
				}
				else
				{
					max_keySet = true;
					max_key = curKey;
					maxKeyEquals = false;
				}
			}
			else
			{
				if(max_valueSet == true)
				{
					if(strcmp(curValue.c_str(), max_value.c_str()) < 0)
					{
						max_value = curValue;
						maxValueEquals = false;
					}
					else if(strcmp(curValue.c_str(), max_value.c_str()) == 0
						&& maxValueEquals == true)
					{
						maxValueEquals = false;
					}
				}
				else
				{
					max_valueSet = true;
					max_value = curValue;
					maxValueEquals = false;
				}
			}
			break;
		case SelCond::GE:
			if(type == 1)
			{
				if(min_keySet == true)
				{
					if(curKey > min_key)
					{
						min_key = curKey;
						minKeyEquals = true;
					}
				}
				else
				{
					min_keySet = true;
					min_key = curKey;
					minKeyEquals = true;
				}
			}
			else
			{
				if(min_valueSet == true)
				{
					if(strcmp(curValue.c_str(), min_value.c_str()) > 0)
					{
						min_value = curValue;
						minValueEquals = true;
					}
				}
				else
				{
					min_keySet = true;
					min_value = curValue;
					minValueEquals = true;
				}
			}
			break;
		case SelCond::LE:
			if(type == 1)
			{
				if(max_keySet == true)
				{
					if(curKey < max_key)
					{
						max_key = curKey;
						maxKeyEquals = true;
					}
				}
				else
				{
					max_keySet = true;
					max_key = curKey;
					maxKeyEquals = true;
				}
			}
			else
			{
				if(max_valueSet == true)
				{
					if(strcmp(curValue.c_str(), max_value.c_str()) < 0)
					{
						max_value = curValue;
						maxValueEquals = true;
					}
				}
				else
				{
					max_keySet = true;
					max_value = curValue;
					maxValueEquals = true;
				}
			}
			break;
		}
	}



checkall:
	count = 0;
	if(min_keySet && max_keySet
		&& (min_key > max_key))
		empty = true;
	if(equalKey)
	{
		if(min_keySet && max_keySet)
		{
			if(minKeyEquals && maxKeyEquals)
			{
				if(key_to_equal < min_key || key_to_equal > max_key)
					empty = true;
			}
			else if(minKeyEquals && !maxKeyEquals)
			{
				if(key_to_equal < min_key || key_to_equal >= max_key)
					empty = true;
			}
			else if(!minKeyEquals && maxKeyEquals)
			{
				if(key_to_equal <= min_key || key_to_equal > max_key)
					empty = true;
			}
			else
			{
				if(key_to_equal <= min_key || key_to_equal >= max_key)
					empty = true;
			}
		}
		else if(min_keySet && !max_keySet)
		{
			if(minKeyEquals)
			{
				if(key_to_equal < min_key)
					empty = true;
			}
			else if(!minKeyEquals)
			{
				if(key_to_equal <= min_key)
					empty = true;
			}
		}
		else if(!min_keySet && max_keySet)
		{
			if(maxKeyEquals)
			{
				if(key_to_equal > max_key)
					empty = true;
			}
			else if(!maxKeyEquals)
			{
				if(key_to_equal >= max_key)
					empty = true;
			}
		}
	}
	if(equalKey)
	{
		//cerr << "equalK" << endl;
		minKeyEquals = true; maxKeyEquals = true;
		min_key = key_to_equal; max_key = key_to_equal;
	}
	if(equalValue)
	{
		if(min_valueSet && max_valueSet)
		{
			if(minValueEquals && maxValueEquals)
			{
				if(strcmp(value_to_equal.c_str(), min_value.c_str()) < 0 || strcmp(value_to_equal.c_str(), max_value.c_str()) > 0)
					empty = true;
			}
			else if(minValueEquals && !maxValueEquals)
			{
				if(strcmp(value_to_equal.c_str(), min_value.c_str()) < 0 || strcmp(value_to_equal.c_str(), max_value.c_str()) >= 0)
					empty = true;
			}
			else if(!minValueEquals && maxValueEquals)
			{
				if(strcmp(value_to_equal.c_str(), min_value.c_str()) <= 0 || strcmp(value_to_equal.c_str(), max_value.c_str()) > 0)
					empty = true;
			}
			else
			{
				if(strcmp(value_to_equal.c_str(), min_value.c_str()) <= 0 || strcmp(value_to_equal.c_str(), max_value.c_str()) >= 0)
					empty = true;
			}
		}
		else if (min_valueSet && !max_valueSet)
		{
			if(minValueEquals)
			{
				if(strcmp(value_to_equal.c_str(), min_value.c_str()) < 0)
					empty = true;
			}
			else if(!minValueEquals)
			{
				if(strcmp(value_to_equal.c_str(), min_value.c_str()) <= 0)
					empty = true;
			}
		}
		else if (!min_valueSet && max_valueSet)
		{
			if(maxValueEquals)
			{
				if(strcmp(value_to_equal.c_str(), max_value.c_str()) > 0)
					empty = true;
			}
			else if(!maxValueEquals)
			{
				if(strcmp(value_to_equal.c_str(), max_value.c_str()) >= 0)
					empty = true;
			}
		}
	}
	if(empty)
	{
		count = 0;
		goto printcount;
	}
	//added
	if(((!min_keySet && !max_keySet && !equalKey) && (!((attr == 1 || attr == 4) && cond.size()==0))))
	{
		//cerr << "inside check" << endl;
		goto original;
	}

	//cerr << "use b+ tree" << endl;
	//cerr << max_key << endl;
	//cerr << indexFile.getMinKey() << " " << indexFile.getMaxKey() << endl;

	IndexCursor indexC;

	//check key
	if(equalKey)
	{
		//cerr << min_key << " " << max_key << " " << key_to_equal << endl;
		if((rc = indexFile.locate(key_to_equal, indexC)) != 0)
		{
			//cerr << "oh no!" << endl;
			goto exit_select;
		}
		
	}
	//find where to start
	else if(min_keySet)
	{
		//cerr << min_key << endl;
		if((rc = indexFile.locate(min_key, indexC)) != 0)
		{
			//cerr << rc << endl;
			goto exit_select;
		}
	}
	//start from the smallest value
	else
	{
		if((rc = indexFile.locate(-1, indexC)) != 0)
		{
			//cerr << "not again!" << endl;
			goto exit_select;
		}
	}
	
	
	//scan table
	while(indexFile.readForward(indexC, key, rid) == 0 && cont)
	{
		//cerr << key_to_equal << " " << key << endl;
		//exit(1);
		//check key
		if(equalKey)
		{
			//cerr << "equal" << endl;
			if(key == key_to_equal)
			{
				//cerr << "oh no!" << endl;
				cont = false;
			}
		}
		else
		{
		if(min_keySet)
		{
			//cerr << "min" << endl;
			if((minKeyEquals && key < min_key)
				|| (!minKeyEquals && key <= min_key))
				continue;
		}
		if(max_keySet)
		{
			//cerr << "max" << endl;
			if((maxKeyEquals && key > max_key)
				|| (!maxKeyEquals && key >= max_key))
			{
				//cerr << "in max" << endl;
				break;
			}
		}
		if(notEqualKey)
		{
			//cerr << "notEqual" << endl;
			bool checkNot = false;
			for(int i = 0; i < keys_to_not_equal.size(); i++)
			{
				if(key == keys_to_not_equal[i])
				{
					checkNot = true;
					break;
				}
			}
			if(checkNot)
				continue;
		}
		}

		//check value, if necessary
		//added
	  if((min_valueSet || max_valueSet || equalValue || notEqualValue) || attr==3 || attr==2)
	  {
		if((rc = rf.read(rid, key, value)) != 0)
			goto exit_select;

		if(equalValue)
		{
			//cerr << "equalV" << endl;
			if(value != value_to_equal)
				continue;
		}
		if(min_valueSet)
		{
			//cerr << "minV" << endl;
			if((minValueEquals && strcmp(value.c_str(), min_value.c_str()) < 0)
				|| (!minValueEquals && strcmp(value.c_str(), min_value.c_str()) <= 0))
				continue;
		}
		if(max_valueSet)
		{
			//cerr << "maxV" << endl;
			if((maxValueEquals && strcmp(value.c_str(), max_value.c_str()) > 0)
				|| (!maxValueEquals && strcmp(value.c_str(), max_value.c_str()) >= min_key))
				continue;
		}
		if(notEqualValue)
		{
			//cerr << "notE" << endl;
			bool checkNot = false;
			for(int i = 0; i < values_to_not_equal.size(); i++)
			{
				if(value == values_to_not_equal[i])
				{
					checkNot = true;
					continue;
				}
			}
			if(checkNot)
				continue;
		}
	  }

		//tuple passed
		count++;

			    // print the tuple 
		switch (attr)
		{
		case 1:  // SELECT key
			fprintf(stdout, "%d\n", key);
			break;
		case 2:  // SELECT value
			fprintf(stdout, "%s\n", value.c_str());
			break;
		case 3:  // SELECT *
			fprintf(stdout, "%d '%s'\n", key, value.c_str());
			break;
		}



	}

  printcount:
  // print matching tuple count if "select count(*)"
  if (attr == 4) {
    fprintf(stdout, "%d\n", count);
  }




  }
  //original implementation
  else
  {
	original:
	  	  //cerr << "not use index" << endl;
  // scan the table file from the beginning
  rid.pid = rid.sid = 0;
  count = 0;
  while (rid < rf.endRid()) {
    // read the tuple
    if ((rc = rf.read(rid, key, value)) < 0) {
      fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
      goto exit_select;
    }

    // check the conditions on the tuple
	//cerr << cond.size() << endl;
    for (unsigned i = 0; i < cond.size(); i++) {
		//if(key == 2244) cerr << i << endl;
      // compute the difference between the tuple value and the condition value
      switch (cond[i].attr) {
      case 1:
	diff = key - atoi(cond[i].value);
	break;
      case 2:
		 // cerr << value << " " << cond[i].value << endl;
	diff = strcmp(value.c_str(), cond[i].value);
	break;
      }

      // skip the tuple if any condition is not met
      switch (cond[i].comp) {
      case SelCond::EQ:
	if (diff != 0)
		{
			//cerr << key << endl;
			goto next_tuple;
		}
	//cerr << key << " " << value << endl;
	break;
      case SelCond::NE:
	if (diff == 0) goto next_tuple;
	break;
      case SelCond::GT:
	if (diff <= 0) goto next_tuple;
	break;
      case SelCond::LT:
	if (diff >= 0) goto next_tuple;
	break;
      case SelCond::GE:
	if (diff < 0) goto next_tuple;
	break;
      case SelCond::LE:
	if (diff > 0) goto next_tuple;
	break;
      }
    }


    // the condition is met for the tuple. 
    // increase matching tuple counter
    count++;

    // print the tuple 
    switch (attr) {
    case 1:  // SELECT key
      fprintf(stdout, "%d\n", key);
      break;
    case 2:  // SELECT value
      fprintf(stdout, "%s\n", value.c_str());
      break;
    case 3:  // SELECT *
		//cerr << "made it" << endl;
      fprintf(stdout, "%d '%s'\n", key, value.c_str());
      break;
    }

    // move to the next tuple
    next_tuple:
    ++rid;
  }



  // print matching tuple count if "select count(*)"
  if (attr == 4) {
    fprintf(stdout, "%d\n", count);
  }
  rc = 0;
  }

  // close the table file and return
exit_select:

  if(useIndex)
	 indexFile.close();
  rf.close();
  return rc;
}

RC SqlEngine::load(const string& table, const string& loadfile, bool index)
{
  ifstream file;
  string line;
  int key;
  string value; 
  //open file
  file.open(loadfile.c_str());
  if(file.is_open())
  {
	  //open RecordFile
	  RecordFile rf;
	  RC rc;
	  //to create an index
	  BTreeIndex indexFile;

	  		  //create index
		  if(index)
		  {
			  rc = indexFile.open(table + ".idx", 'w');
			  if(rc != 0)
			  {
				  return rc;
			  }
		  }

	  if((rc = rf.open(table + ".tbl", 'w')) == 0)
	  {

		  //insert tuples
		while(file.good() && getline(file, line))
		{
		  //getline(file, line);
		  if (SqlEngine::parseLoadLine(line, key, value)==0)
		  {
			RecordId rid;
			if(rf.append(key, value, rid) == 0)
			{
				if(index)
				{
					rc = indexFile.insert(key, rid);
					if(rc != 0)
					{
						return rc;
					}
				}
			}
			else
				return RC_INVALID_ATTRIBUTE;
		  }
		  
		}
		if(index)
		{
			indexFile.close();
		}
		rc = rf.close();
	  }
	  else
		  return RC_FILE_OPEN_FAILED;

  }
  else
	  return RC_FILE_OPEN_FAILED;
  file.close();

  return 0;
}

RC SqlEngine::parseLoadLine(const string& line, int& key, string& value)
{
    const char *s;
    char        c;
    string::size_type loc;
    
    // ignore beginning white spaces
    c = *(s = line.c_str());
    while (c == ' ' || c == '\t') { c = *++s; }

    // get the integer key value
    key = atoi(s);

    // look for comma
    s = strchr(s, ',');
    if (s == NULL) {
		cout << "s == NULL" << endl;
		return RC_INVALID_FILE_FORMAT; 
	}

    // ignore white spaces
    do { c = *++s; } while (c == ' ' || c == '\t');
    
    // if there is nothing left, set the value to empty string
    if (c == 0) { 
        value.erase();
        return 0;
    }

    // is the value field delimited by ' or "?
    if (c == '\'' || c == '"') {
        s++;
    } else {
        c = '\n';
    }

    // get the value string
    value.assign(s);
    loc = value.find(c, 0);
    if (loc != string::npos) { value.erase(loc); }

    return 0;
}
